package jsu.blogger.service;
import jsu.blogger.config.RedisKeyPrefixConfig;
import jsu.blogger.util.ObjectProcessingUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Service
public class AdvancedRedisService {
    private static final Logger logger = LoggerFactory.getLogger(AdvancedRedisService.class);
    @Autowired
    private RedisTemplate<String,Object>redisTemplate;

    /**
     * 通用的将对象存储到 Redis Hash 中的方法
     */
    public <T> void saveObjectToRedisHash(String hashKey, T object) {
        // 获取 Hash 操作对象
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        // 使用反射自动将对象转化为 Map
        Map<String, Object> objectMap = ObjectProcessingUtil.extractFieldsToMap(object);
        // 将 Map 存储到 Redis 哈希表中
        hashOps.putAll(hashKey, objectMap);
    }
    /**
     * 将对象存储到 Redis 哈希表中并设置过期时间
     */
    public <T> void saveObjectToRedisHashWithExpiration(String hashKey, T object, long timeout,TimeUnit timeUnit) {
        // 获取 Hash 操作对象
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        // 使用反射自动将对象转化为 Map
        Map<String, Object> objectMap = ObjectProcessingUtil.extractFieldsToMap(object);
        // 将 Map 存储到 Redis 哈希表中
        hashOps.putAll(hashKey, objectMap);
        // 设置过期时间，单位为秒
        redisTemplate.expire(hashKey, timeout, timeUnit);
    }
    //hash版缓存穿透
    public <R, ID> R queryWithHashPassThrough(
            String keyPrefix,
            ID id,
            Class<R> type,
            Function<ID, R> dbFallback,
            long timeout,
            TimeUnit timeUnit
    )  {
        try {
            // 1. 构造缓存的 key
            String key = keyPrefix + id;
            // 2. 查询 Redis 哈希表
            Map<Object, Object> hash = this.getAllFromHash(key);
            // 3. 如果缓存中有数据，且标志位有效，则直接返回
            if (hash != null && !hash.isEmpty()) {
                String isValid = (String) hash.get("isValid");
                if (isValid == null) {
                    // 如果标记为有效，获取数据
                    return ObjectProcessingUtil.mapToObjectIncludeSuper(hash, type);
                }
            }
            // 4. 如果缓存是空哈希或标志位为无效，说明数据库查询结果为空
            if (hash != null && hash.get("isValid") != null && "false".equals(hash.get("isValid"))) {
                return null;  // 返回 null，表示数据为空或缓存数据无效
            }
            // 5.jpg. 如果缓存中没有数据，则查询数据库
            R r = dbFallback.apply(id);
            // 6. 如果数据库查询结果是 null，表示没有数据，写入空值标记
            if (r == null) {
                // 使用 addToHash 存入一个有效值
                this.addToHash(key,"isValid","false");
                setHashWithExpiration(key, RedisKeyPrefixConfig.HASH_PASS_THROUGH_KEY_EXPIRE,TimeUnit.MINUTES);
                return null;  // 返回 null，表示数据库也没有数据
            }
            // 7. 如果数据库查询结果有数据，返回Object
            // 使用 saveObjectToRedisHashWithExpiration 存储数据到 Redis 哈希表，并设置过期时间
            this.saveObjectToRedisHashWithExpiration(key, r, timeout, timeUnit);
            // 8. 返回从数据库查询到的数据
            return r;
        }catch (Exception e){
            logger.error("queryWithHashPassThrough 缓存穿透失败 "+e);
            return null;
        }
    }

    /**
     * 获取 Redis 哈希表中的所有数据
     *
     * @param hashKey Redis 哈希表的键
     * @return 返回整个哈希表的数据
     */
    public Map<Object, Object> getAllFromHash(String hashKey) {
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        return hashOps.entries(hashKey);  // 获取整个哈希表
    }
    //向hash 添加
    public void addToHash(String hashKey, String field, String value) {
        // 获取 Redis 哈希表操作对象
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        // 向指定的哈希表中添加单个元素
        hashOps.put(hashKey, field, value);
    }
    //设置过期时间
    public void setHashWithExpiration(String hashKey, long timeout, TimeUnit timeUnit) {
        // 获取 Hash 操作对象
        // HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        // 设置哈希表的过期时间（单位是秒或毫秒）
        redisTemplate.expire(hashKey, timeout, timeUnit);  // 设置整个哈希表的过期时间
    }

    public void updateHashKeyValue(String HASH_KEY,String key,Object newVal) {
        // 获取 HashOperations 对象
        HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
        // 使用 put() 方法修改 Hash 中的某个 key 的值
        hashOperations.put(HASH_KEY, key, newVal);
    }
}
